﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using Daybreak.Core.Rendering.Pipeline;

namespace Daybreak.Core.Rendering.Materials
{
    /// <summary>
    /// Describes HLSL shader effect
    /// </summary>
    public abstract class Shader
    {
        #region param value classes

        public abstract class Value
        {
            public abstract void SetValueTo(EffectParameter param);
            public abstract void GetValueFrom(EffectParameter param);

        }

        public class TextureValue : Value
        {
            public Texture Value;

            public TextureValue()
            {
            }

            public override void SetValueTo(EffectParameter param)
            {
                param.SetValue(Value);
            }

            public override void GetValueFrom(EffectParameter param)
            {
                if (Value != null)
                    return;

                if (param.ParameterType == EffectParameterType.Texture)
                    Value = param.GetValueTexture2D();

                if (param.ParameterType == EffectParameterType.Texture2D)
                    Value = param.GetValueTexture2D();

                if (param.ParameterType == EffectParameterType.Texture3D)
                    Value = param.GetValueTexture3D();

                if (param.ParameterType == EffectParameterType.TextureCube)
                    Value = param.GetValueTextureCube();
            }

        }

        public class BoolValue : Value
        {
            public bool Value;

            public BoolValue()
            {
            }

            public override void SetValueTo(EffectParameter param)
            {
                param.SetValue(Value);
            }

            public override void GetValueFrom(EffectParameter param)
            {
                Value = param.GetValueBoolean();
            }
        }

        public class FloatValue : Value
        {
            public float Value;

            public FloatValue()
            {
            }

            public override void SetValueTo(EffectParameter param)
            {
                param.SetValue(Value);
            }

            public override void GetValueFrom(EffectParameter param)
            {
                Value = param.GetValueSingle();
            }
        }

        public class Float2Value : Value
        {
            public Vector2 Value;

            public Float2Value()
            {
            }

            public override void SetValueTo(EffectParameter param)
            {
                param.SetValue(Value);
            }

            public override void GetValueFrom(EffectParameter param)
            {
                Value = param.GetValueVector2();
            }
        }

        public class Float3Value : Value
        {
            public Vector3 Value;

            public Float3Value()
            {
            }

            public override void SetValueTo(EffectParameter param)
            {
                param.SetValue(Value);
            }

            public override void GetValueFrom(EffectParameter param)
            {
                Value = param.GetValueVector3();
            }
        }

        public class Float4Value : Value
        {
            public Vector4 Value;

            public Float4Value()
            {
            }

            public override void SetValueTo(EffectParameter param)
            {
                param.SetValue(Value);
            }

            public override void GetValueFrom(EffectParameter param)
            {
                Value = param.GetValueVector4();
            }
        }

        public class ColorValue : Value
        {
            public Color Value;

            public ColorValue()
            {
            }

            public override void SetValueTo(EffectParameter param)
            {
                if (param.ParameterType == EffectParameterType.Int32)
                    param.SetValue(Value.PackedValue);
                else
                {
                    if (param.ColumnCount == 4)
                        param.SetValue(Value.ToVector4());

                    if (param.ColumnCount == 3)
                        param.SetValue(Value.ToVector3());
                }
            }

            public override void GetValueFrom(EffectParameter param)
            {
                if (param.ParameterType == EffectParameterType.Int32)
                    Value.PackedValue = (uint)param.GetValueInt32();
                else
                    if (param.ParameterType == EffectParameterType.Single && 
                        param.ParameterClass == EffectParameterClass.Vector)
                    {
                    if (param.ColumnCount == 4)
                        Value = new Color(param.GetValueVector4());

                    if (param.ColumnCount == 3)
                        Value = new Color(param.GetValueVector3());
                }
            }
        }

        public class MatrixValue : Value
        {
            public Matrix Value;

            public MatrixValue()
            {
            }

            public override void SetValueTo(EffectParameter param)
            {
                param.SetValue(Value);
            }

            public override void GetValueFrom(EffectParameter param)
            {
                Value = param.GetValueMatrix();
            }
        }

        #endregion
        
        /// <summary>
        /// Represents an Effect parameter base class
        /// </summary>
        public abstract class Parameter
        {
            public EffectParameter Param;
            public SasSemantic Semantic;

            public Parameter(EffectParameter parameter)
            {
                Param = parameter;
                Semantic = SasStorage.GetKnownSemantic(parameter.Semantic);

            }

            public Parameter(EffectParameter parameter, SasSemantic semantic)
            {
                Param = parameter;
                Semantic = semantic;
            }

            public abstract void CommitValue();

            public static bool HasSemantic(EffectParameter param, SasSemantic semantic)
            {
                string sem = param.Semantic;

                if (string.IsNullOrEmpty(sem))
                    return false;

                return string.Compare(sem, semantic.ToString(), true) == 0;

            }

            public static bool HasUiWidget(EffectParameter param, out string uiType, out string uiName)
            {
                uiType = null;
                uiName = null;

                EffectAnnotation uiWidAnn = param.Annotations["UIWidget"];
                EffectAnnotation uiNameAnn = param.Annotations["UIName"];

                if (uiWidAnn == null && uiNameAnn == null)
                    return false;

                if (uiNameAnn != null)
                    uiName = uiNameAnn.GetValueString();

                if (uiWidAnn == null)
                    return true;

                string ui = uiWidAnn.GetValueString();

                if (string.IsNullOrEmpty(ui))
                    return false;

                if (string.Compare(ui, "none", true) == 0)
                    return false;

                uiType = ui;

                return true;

            }

            public EffectAnnotationCollection Annotations
            {
                get { return Param.Annotations; }
            }
        }

        /// <summary>
        /// Represents an Effect parameter with semantic
        /// </summary>
        public class ConnectedParameter : Parameter
        {
            public SceneObject ObjectClass;

            private Pipes.Var pipeVariable;

            public ConnectedParameter(EffectParameter parameter)
                : base(parameter)
            {
                Semantic = SasStorage.GetKnownSemantic(parameter.Semantic);
                ObjectClass = SceneObject.Any;
                pipeVariable = Pipes.GetVariable(ObjectClass, Semantic);

            }

            public ConnectedParameter(EffectParameter parameter, SasSemantic semantic)
                : base(parameter, semantic)
            {
                ObjectClass = SceneObject.Any;
                pipeVariable = Pipes.GetVariable(ObjectClass, Semantic);
            }

            public ConnectedParameter(EffectParameter parameter, SasSemantic semantic, SceneObject obj)
                : base(parameter, semantic)
            {
                ObjectClass = obj;
                pipeVariable = Pipes.GetVariable(ObjectClass, Semantic);
            }

            public override void CommitValue()
            {
                pipeVariable.SetValueTo(this.Param);
            }

            public override string ToString()
            {
                string tt = Param.ParameterClass + "." + Param.ParameterType;

                return string.Format("{0} {1} : {2} <Object = '{3}'>", tt, Param.Name, Semantic, ObjectClass);
            }

        }

        /// <summary>
        /// Represents an Effect material tweakable parameter with semantic
        /// </summary>
        public class TweakableParameter : Parameter
        {
            public Value Data;
            public string UiWidget;
            public string UiName;

            public TweakableParameter(EffectParameter parameter)
                :base(parameter)
            {
                Semantic = SasStorage.GetKnownSemantic(parameter.Semantic);
            }

            public TweakableParameter(EffectParameter parameter, SasSemantic semantic)
                :base(parameter, semantic)
            {
            }

            public override void CommitValue()
            {
                Data.SetValueTo(Param);
            }

            public override string ToString()
            {
                string tt = Data.GetType().Name;

                return string.Format("{0} {1} : {2}", tt, Param.Name, Semantic);
            }
        }

        protected Effect _effect;
        private List<ConnectedParameter> _objectParamList;
        private List<ConnectedParameter> _frameParamList;
        private List<TweakableParameter> _effectParamList;

        //internal int Index;

        public Shader()
        {
        }

        public void Begin()
        {
            _effect.Begin();
        }

        public void End()
        {
            _effect.End();
        }

        public T FindParam<T>(SasSemantic sem, int index) where T : Value
        {
            int found = -1;

            T result = null;

            for (int i = 0; i < MaterialParameterList.Count; i++)
            {
                if (MaterialParameterList[i].Semantic == sem)
                {
                    result = MaterialParameterList[i].Data as T;

                    if (result != null)
                    {
                        found++;

                        if (found == index)
                            return result;
                    }

                }

            }

            return null;

        }

        public void CommitFrameChanges()
        {
            if (_frameParamList == null)
                return;

            for (int i = 0; i < _frameParamList.Count; i++)
            {
                _frameParamList[i].CommitValue();
            }
        }

        public void CommitObjectChanges()
        {
            if (_objectParamList == null)
                return;

            for (int i = 0; i < _objectParamList.Count; i++)
            {
                _objectParamList[i].CommitValue();
            }
        }

        public void CommitMaterialChanges()
        {
            if (_effectParamList == null)
                return;

            for (int i = 0; i < _effectParamList.Count; i++)
            {
                _effectParamList[i].CommitValue();
            }
        }

        /// <summary>
        /// Adds Material Color Attribute
        /// </summary>
        /// <param name="parameter"></param>
        /// <param name="semantic"></param>
        protected TweakableParameter AddColor(EffectParameter parameter, SasSemantic semantic)
        {
            return AddMaterialAttribute(parameter, semantic, new ColorValue());
        }

        /// <summary>
        /// Adds Material Texture Attribute
        /// </summary>
        /// <param name="parameter"></param>
        /// <param name="semantic"></param>
        protected TweakableParameter AddTexture(EffectParameter parameter, SasSemantic semantic)
        {
            return AddMaterialAttribute(parameter, semantic, new TextureValue());
        }

        /// <summary>
        /// Adds Material Float (single) Attribute
        /// </summary>
        /// <param name="parameter"></param>
        /// <param name="semantic"></param>
        protected TweakableParameter AddFloat(EffectParameter parameter, SasSemantic semantic)
        {
            return AddMaterialAttribute(parameter, semantic, new FloatValue());
        }

        protected TweakableParameter AddMaterialAttribute(EffectParameter parameter, SasSemantic semantic)
        {
            if (parameter.ParameterType == EffectParameterType.Texture ||
                parameter.ParameterType == EffectParameterType.Texture1D ||
                parameter.ParameterType == EffectParameterType.Texture2D ||
                parameter.ParameterType == EffectParameterType.Texture3D ||
                parameter.ParameterType == EffectParameterType.TextureCube )
            {
                return AddMaterialAttribute(parameter, semantic, new TextureValue());
            }

            if (parameter.ParameterType == EffectParameterType.Bool)
            {
                return AddMaterialAttribute(parameter, semantic, new BoolValue());
            }

            if (parameter.ParameterType == EffectParameterType.Single &&
                parameter.ParameterClass == EffectParameterClass.Scalar )
            {
                return AddMaterialAttribute(parameter, semantic, new FloatValue());
            }

            if (parameter.ParameterType == EffectParameterType.Single &&
                parameter.ParameterClass == EffectParameterClass.Vector)
            {
                if (parameter.ColumnCount == 1)
                    return AddMaterialAttribute(parameter, semantic, new FloatValue());

                if (parameter.ColumnCount == 2)
                    return AddMaterialAttribute(parameter, semantic, new Float2Value());

                if (parameter.ColumnCount == 3)
                    return AddMaterialAttribute(parameter, semantic, new Float3Value());

                if (parameter.ColumnCount == 4)
                    return AddMaterialAttribute(parameter, semantic, new Float4Value());
            }

            return null;

        }

        protected TweakableParameter AddMaterialAttribute(EffectParameter parameter, SasSemantic semantic, Value val)
        {
            if (semantic == SasSemantic._unknown)
                semantic = SasSemantic.none;

            TweakableParameter p = new TweakableParameter(parameter, semantic);
            p.Data = val;

            if (_effectParamList == null)
                _effectParamList = new List<TweakableParameter>();

            _effectParamList.Add(p);

            if (val != null)
                val.GetValueFrom(parameter);

            return p;
        }

        protected void AddObjectAttribute(EffectParameter parameter, SasSemantic semantic)
        {
            if (_objectParamList == null)
                _objectParamList = new List<ConnectedParameter>();

            _objectParamList.Add(new ConnectedParameter(parameter, semantic, SceneObject.Any));

        }
        protected void AddFrameAttribute(EffectParameter parameter, SasSemantic semantic, SceneObject obj)
        {
            if (_frameParamList == null)
                _frameParamList = new List<ConnectedParameter>();

            _frameParamList.Add(new ConnectedParameter(parameter, semantic, obj));
        }

        public abstract bool UseTangents { get; }

        public List<TweakableParameter> MaterialParameterList
        {
            get { return _effectParamList; }
        }

        /// <summary>
        /// Gets shader effect
        /// </summary>
        public Effect Effect
        {
            get { return _effect; }
        }
    }
}
